/*
 * @features: 代理核心
 * @description: 说明
 * @Date: 2021-09-06 22:07:23
 * @Author: judu233(769471424@qq.com)
 * @LastEditTime: 2021-10-03 00:38:15
 * @LastEditors: judu233
 */

import { director, sys } from "cc";
import { EDITOR } from "cc/env";
import c3d from "../../../cc";

/**调试输出路径 */
const DEBUG_SHOW_PATH = false;
/**发射消息头 */
enum ProxyEmitHead {
    DataProxy_,
}
/**遍历时保存的路径 */
const ProxyPathTag = `_$ProxyPathTag`;
interface Proxy_Data {
    /**代理的tag */
    tag: string,
    /**原始代理目标 */
    target: any;
    /**操控对象 */
    proxy: any;
    /**代理对象 */
    proxyOb: ProxyOb<any>;
}
export class ProxyOb<T>{
    /**保存创建的代理信息信息 */
    private static _proxys: Map<string, Proxy_Data> = new Map();

    /**代理操作对象 */
    proxy: T;

    /**激活状态, 将会通过 cc.director.emit 发送值变动的信号, 适合需要屏蔽的情况 */
    _active: boolean = true;

    /**是否激活根路径回调通知, 不激活的情况下 只能监听末端路径值来判断是否变化 */
    emitToRootPath: boolean = false;

    /**是否设置值时触发检查是否相等 (不建议数值开启)， 由于太小的数值变动，列如滑动组件的progress，会导致判定失效，所以需要关闭*/
    isCheckValue = true;

    constructor(
        /**代理的原始对象 */
        public target: T,
        /** tag*/
        public tagPath: string,
        /** 是否深度绑定监听*/
        public isDeepBind = true,
        /**发射的事件头 */
        public emitHead: keyof typeof ProxyEmitHead = "DataProxy_",
        /** 自定义代理句柄*/
        diyProxy?: ProxyHandler<any>,
        /**自定义绑定监听函数 */
        bindFun?: (obj: any, path: string, isDeepBind: boolean, proxySet: any) => any
    ) {
        if (EDITOR) return;
        this.proxy = bindFun ? bindFun(target, tagPath, isDeepBind, diyProxy) : this.bindProxy(target, tagPath, isDeepBind, diyProxy);
        if (!ProxyOb.has(tagPath))
            ProxyOb._proxys.set(tagPath, { tag: tagPath, proxy: this.proxy, target, proxyOb: this });
        else
            console.warn(`已经有${tagPath}代理，不在存入`);
        return this;
    }

    /**
     * 绑定代理对象
     * @param obj 代理目标
     * @param path 遍历保存的路径
     * @param isDeepBind 是否深度遍历
     * @param proxySet 代理句柄
     * @returns 代理对象
     */
    private bindProxy(obj: any, path: string, isDeepBind: boolean, proxySet: any, refSet: Set<any> = new Set()) {
        let self = this;
        //过滤掉不是object 和数组的
        if (obj != null && (typeof obj == 'object' || typeof obj == 'function')) {
            if (isDeepBind) {
                //过滤掉特殊属性，防止堆栈溢出
                let filterArr = [
                    obj instanceof c3d.Node,
                    obj instanceof c3d.Sprite,
                    obj instanceof c3d.SpriteFrame,
                    obj instanceof c3d.EditBoxComponent._EditBoxImpl,
                    obj[`_$ProxyPathTag`] != null
                ];
                if (!filterArr.includes(true) && !refSet.has(obj)) {
                    refSet.add(obj);
                    for (let [key, value] of Object.entries(obj)) {
                        obj[key] = this.bindProxy(value, `${path}.${key}`, isDeepBind, proxySet, refSet);
                    }
                }
            }
            //创建代理
            let p = new Proxy(obj, proxySet ?? {
                get(obj, key, receiver) { //receiver 接收者
                    return Reflect.get(obj, key, receiver);
                },
                set(obj, key, newValue, receiver) {
                    let oldValue = Reflect.get(obj, key, receiver);
                    if (self._active && (!self.isCheckValue || oldValue != newValue)) {
                        if (DEBUG_SHOW_PATH) console.log('>>', newValue, oldValue);
                        self.proxyValueChangeCall(newValue, oldValue, key, receiver);
                    }
                    let result = Reflect.set(obj, key, newValue, receiver);
                    return result;
                }
            });
            /**保存数据路径 */
            p[ProxyPathTag] = path;
            return p;
        }
        return obj;
    }

    /**代理的对象值改变的回调 */
    proxyValueChangeCall(newValue: any, oldValue: any, key: any, receiver) {
        let path = receiver[ProxyPathTag];
        if (!!path) {
            path = path + `.` + (key as string);
            let name = `${this.emitHead}${path}`;
            director.emit(name, newValue, oldValue, path);
        }
    }

    /**通过 .路径 设置代理的值 */
    setValueByPath(path: string, value: any) {
        let obj = this.proxy;
        let tag = this.tagPath;
        let props = path.split('.');
        for (let i = 0; i < props.length; i++) {
            const propName = props[i];
            if (propName in obj === false) { console.error('[' + propName + '] not find in ' + tag + '.' + path); break; }
            if (i == props.length - 1) {
                obj[propName] = value;
            } else {
                obj = obj[propName];
            }
        }
    }
    /**通过 . 路径 获取代理的值 */
    getValueByPath(path: string, def?: any): any {
        let obj = this.proxy;
        let tag = this.tagPath;
        let props = path.split('.');
        for (let i = 0; i < props.length; i++) {
            const propName = props[i];
            if ((propName in obj === false)) { console.error('[' + propName + '] not find in ' + tag + '.' + path); return def; }
            obj = obj[propName];
        }
        if (obj === null || typeof obj === "undefined") obj = def;//如果g == null 则返回一个默认值
        return obj;
    }

    /**
       * 通过全局路径,而不是 VM 对象来 获取值
       * @param path - 全局取值路径
       * @param def - 如果取不到值的返回的默认值
       */
    static getValue(path: string, def?: any): any {
        let rs = this._delPath(path);
        let proxyData = this.get(rs[0]);
        if (!proxyData) { throw Error('Cant Get VM:' + rs[0]); }
        return proxyData.proxyOb.getValueByPath(rs.slice(1).join('.'), def);
    }

    /**
     * 通过全局路径,而不是 VM 对象来 设置值
     * @param path - 全局取值路径
     * @param value - 需要设置的值
     */
    static setValue(path: string, value: any) {
        let rs = this._delPath(path);
        let proxyData = this.get(rs[0]);
        if (!proxyData) { throw Error('Cant Set VM:' + rs[0]); }
        proxyData.proxyOb.setValueByPath(rs.slice(1).join('.'), value);
    }

    /**
     * 处理路径
     * @param path 路径
     * @returns 
     */
    static _delPath(path: string) {
        path = path.trim();//防止空格,自动剔除
        let rs = path.split('.');
        if (rs.length < 2) { throw Error('Value Cant find path:' + path); }
        return rs;
    }

    /**
     * 是否有VM对象
     * @param tag 
     */
    static has(tag: string) { return ProxyOb._proxys.has(tag); }

    /**
    * 移除销毁 VM 对象
    * @param tag 
    */
    static remove(tag: string) { return ProxyOb._proxys.delete(tag); }

    /**
     * 获取指定代理proxy
     * @param tag 数据tag
     */
    static get(tag: string) {
        let preoxy = ProxyOb._proxys.get(tag);
        if (preoxy == null) console.error('cant find VM from:', tag);
        else return preoxy;
    }

    /**激活所有标签的 VM*/
    static activeAll(active = true) {
        ProxyOb._proxys.forEach(p => {
            p.proxy._active = active;
        })
    }

    /**绑定监听的事件map */
    private static _bindPathMap: Map<string, { callback: Function, target }> = new Map();

    /**注册事件的监听 */
    static bindPath(emitHead: keyof typeof ProxyEmitHead, path: string, callback: (...any: any[]) => void, target?: any) {
        path = path.trim();//防止空格,自动剔除
        if (path == '') {
            console.error(target.node.name, '节点绑定的路径为空');
            return;
        }
        this._bindPathMap.set(path, { callback, target });
        director.on(emitHead + path, callback, target);
    }

    /**取消事件监听 */
    static unBindPath(mitHead: keyof typeof ProxyEmitHead, path: string, callback: (...any: any[]) => void, target?: any) {
        path = path.trim();//防止空格,自动剔除
        if (path == '') {
            console.error(target.node.name, '节点取消的路径为空');
            return;
        }
        this._bindPathMap.delete(path);
        director.off(mitHead + path, callback, target);
    }

}
